/**
 * @file lv_watch_tabview.c
 *
 */

/*********************
 *      INCLUDES
 *********************/
#include "lv_watch.h"

#if USE_LV_WATCH_TABVIEW != 0

#include "lvgl/src/lv_themes/lv_theme.h"
#include "lvgl/src/lv_misc/lv_anim.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/*********************
 *      DEFINES
 *********************/
#if LV_USE_ANIMATION
#  ifndef LV_TABVIEW_ANIM_TIME
#    define LV_TABVIEW_ANIM_TIME  300 /*Animation time of focusing to the a list element [ms] (0: no animation)  */
#  endif
#else
#  undef  LV_TABVIEW_ANIM_TIME
#  define LV_TABVIEW_ANIM_TIME  0   /*No animations*/
#endif

/**********************
 *      TYPEDEFS
 **********************/
#define LV_WATCH_TABVIEW_LED_ON_WIDTH 8
#define LV_WATCH_TABVIEW_LED_OFF_WIDTH 6

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lv_tabview_set_tab_act_finish_callback(lv_anim_t * a);
static lv_res_t tabpage_signal(lv_obj_t * tab_page, lv_signal_t sign, void * param);
static lv_res_t tabpage_signal_from_element(lv_obj_t * tab_page, lv_signal_t sign, void * param, lv_obj_t * element);
static lv_res_t tabpage_scrl_signal(lv_obj_t * tab_scrl, lv_signal_t sign, void * param);
static lv_res_t element_signal_func(lv_obj_t * element, lv_signal_t sign, void * param);
static void tabpage_pressed_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element);
static void tabpage_pressing_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element);
static void tabpage_press_lost_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element);
static void tabpage_long_press_handler(lv_obj_t * tabview, lv_obj_t * tabpage);
static void tabpage_start_edge_flash(lv_obj_t * tabview, lv_obj_t * tabpage);
static void lv_watch_tabview_leds_update(lv_obj_t * tabview);
static lv_watch_tabview_element_free_ptr_client_t * element_get_ext_client(lv_obj_t * element);

/**********************
 *  STATIC VARIABLES
 **********************/
static lv_signal_cb_t ancestor_signal;
static lv_signal_cb_t page_signal;
static lv_signal_cb_t page_scrl_signal;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

/**
 * Create a Tab view object
 * @param par pointer to an object, it will be the parent of the new tab
 * @param copy pointer to a tab object, if not NULL then the new object will be copied from it
 * @return pointer to the created tab
 */
lv_obj_t * lv_watch_tabview_create(lv_obj_t * par)
{
    LV_LOG_TRACE("watch tab view create started");

    /*Create the ancestor of tab*/
    lv_obj_t * new_tabview = lv_obj_create(par, NULL);
    LV_ASSERT_MEM(new_tabview);
    if(new_tabview == NULL) return NULL;
    if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(new_tabview);

    /*Allocate the tab type specific extended data*/
    lv_watch_tabview_ext_t * ext = lv_obj_allocate_ext_attr(new_tabview, sizeof(lv_watch_tabview_ext_t));
    LV_ASSERT_MEM(ext);
    if(ext == NULL) return NULL;
    memset(ext, 0, sizeof(lv_watch_tabview_ext_t));

    /*Initialize the allocated 'ext' */
    ext->drag_hor = 0;
    ext->draging = 0;
    ext->slide_enable = 1;
    ext->tab_cur = 0;
    ext->point_last.x = 0;
    ext->point_last.y = 0;
    ext->point_start.x = 0;
    ext->point_start.y = 0;
    ext->point_now.x = 0;
    ext->point_now.y = 0;
    ext->hor_drag_start = 2;
    ext->ver_drag_start = 5;
    ext->content = NULL;
    ext->anim_time = LV_TABVIEW_ANIM_TIME;
    ext->element_click_en = 1;
    ext->page_hor_edge_flash_en = 1;
    ext->hor_sliding_loop_en = 0;
    ext->long_press_cb = NULL;
    ext->slide_up_pressing_cb = NULL;
    ext->slide_down_pressing_cb = NULL;
    ext->slide_up_press_lost_cb = NULL;
    ext->slide_down_press_lost_cb = NULL;
    ext->slide_left_press_lost_cb = NULL;
    ext->slide_right_press_lost_cb = NULL;
    ext->is_drag_up = 0;
    ext->is_drag_right = 0;
    ext->show_leds_en = 0;
    ext->leds_content = NULL;
    ext->leds = NULL;

    /*The signal and design functions are not copied so set them here*/
    lv_obj_set_signal_cb(new_tabview, lv_watch_tabview_signal);

    /*Init the new tab*/
    ext->tab_cnt = 0;

    lv_obj_set_size(new_tabview, LV_HOR_RES, LV_VER_RES);

    ext->content = lv_cont_create(new_tabview, NULL);
    lv_cont_set_fit4(ext->content, LV_FIT_MAX, LV_FIT_MAX, LV_FIT_NONE, LV_FIT_NONE);
    lv_cont_set_layout(ext->content, LV_LAYOUT_ROW_TOP);
    lv_obj_add_style(ext->content, LV_CONT_PART_MAIN, &lv_style_transp_tight);
    lv_obj_set_height(ext->content, LV_VER_RES);

    /*Set the default styles*/
    if(lv_theme_get_act()) {
        lv_theme_apply(new_tabview, LV_THEME_OBJ);
    } else {
        lv_obj_add_style(new_tabview, LV_TABVIEW_PART_BG, &lv_style_pretty);
    }

    LV_LOG_INFO("watch tab view created");

    return new_tabview;
}

/**
 * Delete all children of the scrl object, without deleting scrl child.
 * @param obj pointer to an object
 */
void lv_watch_tabview_clean(lv_obj_t * obj)
{
    lv_obj_t * scrl = lv_page_get_scrl(obj);
    lv_obj_clean(scrl);
}

void lv_watch_tabview_del(lv_obj_t * tabview)
{
    lv_watch_png_cache_all_free();
    lv_obj_del(tabview);
}

/*======================
 * Add/remove functions
 *=====================*/

/**
 * Add a new tab with the given name
 * @param tabview pointer to Tab view object where to ass the new tab
 * @return pointer to the created page object (lv_page). You can create your content here
 */
lv_obj_t * lv_watch_tabview_add_tab(lv_obj_t * tabview)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);

    /*Create the container page*/
    lv_obj_t * h = lv_page_create(ext->content, NULL);
    lv_obj_set_size(h, lv_obj_get_width(tabview), lv_obj_get_height(ext->content));
    lv_page_set_scrollbar_mode(h, LV_SCROLLBAR_MODE_OFF);

    lv_obj_add_style(h, LV_PAGE_PART_BG, &lv_style_transp);
    lv_obj_add_style(h, LV_PAGE_PART_SCROLLABLE, &lv_style_transp_tight);
    lv_obj_add_style(h, LV_PAGE_PART_SCROLLBAR, &lv_style_transp);
    lv_obj_add_style(h, LV_PAGE_PART_EDGE_FLASH, &lv_watch_cont_opa1);

    if(page_signal == NULL) page_signal = lv_obj_get_signal_cb(h);
    if(page_scrl_signal == NULL) page_scrl_signal = lv_obj_get_signal_cb(lv_page_get_scrl(h));
    lv_obj_set_signal_cb(h, tabpage_signal);
    lv_obj_set_signal_cb(lv_page_get_scrl(h), tabpage_scrl_signal);

    ext->tab_cnt++;

    /*Set the first btn as active*/
    if(ext->tab_cnt == 1) {
        ext->tab_cur = 0;
        lv_watch_tabview_set_tab_act(tabview, 0, false);
    }

    return h;
}

void lv_watch_tabview_add_element(lv_obj_t * element)
{
    lv_watch_tabview_add_element_with_special(element, NULL);
}

void lv_watch_tabview_add_element_with_special(lv_obj_t * element, lv_watch_tabview_element_free_ptr_client_t * attribute)
{
    lv_watch_set_free_ptr(element, lv_obj_get_signal_cb(element));
    lv_watch_set_client_ptr(element, attribute);
    lv_obj_set_signal_cb(element, element_signal_func);
    //lv_obj_set_drag_parent(element, true);
    lv_obj_add_protect(element, LV_PROTECT_PRESS_LOST | LV_PROTECT_CLICK_FOCUS);
}

/*=====================
 * Setter functions
 *====================*/
/**
 * Set a new tab
 * @param tabview pointer to Tab view object
 * @param id index of a tab to load
 * @param anim_en true: set with sliding animation; false: set immediately
 */
void lv_watch_tabview_set_tab_act(lv_obj_t * tabview, uint16_t id, bool anim_en)
{
#if LV_USE_ANIMATION == 0
    anim_en = false;
#endif
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);

    if(id >= ext->tab_cnt) id = ext->tab_cnt - 1;
    ext->tab_cur = id;

    void (*cb)(lv_anim_t *) = lv_tabview_set_tab_act_finish_callback;
    uint8_t pad_inner = lv_obj_get_style_pad_inner(ext->content, LV_OBJ_PART_MAIN);
    uint8_t pad_left = lv_obj_get_style_pad_left(ext->content, LV_OBJ_PART_MAIN);
    lv_coord_t cont_x = -(lv_obj_get_width(tabview) * id + pad_inner * id + pad_left);
    if(ext->anim_time == 0 || anim_en == false) {
        lv_obj_set_x(ext->content, cont_x);
        if(tabview->signal_cb == lv_watch_tabview_signal) {
            ext->element_click_en = 1;
            lv_watch_tabview_leds_update(tabview);
        }
    } else {
#if LV_USE_ANIMATION
        lv_anim_path_t path;
        lv_anim_path_init(&path);
        lv_anim_path_set_cb(&path, lv_anim_path_linear);
        lv_anim_t a;
        lv_anim_init(&a);
        lv_anim_set_var(&a, ext->content);
        lv_anim_set_values(&a, lv_obj_get_x(ext->content), cont_x);
        lv_anim_set_exec_cb(&a, (lv_anim_exec_xcb_t)lv_obj_set_x);
        lv_anim_set_ready_cb(&a, (lv_anim_ready_cb_t)cb);
        lv_anim_set_time(&a, ext->anim_time);
        lv_anim_set_path(&a, &path);
        lv_anim_start(&a);
#endif
    }
}

/**
 * Enable horizontal sliding with touch pad
 * @param tabview pointer to Tab view object
 * @param en true: enable sliding; false: disable sliding
 */
void lv_watch_tabview_set_sliding(lv_obj_t * tabview, bool en)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);
    ext->slide_enable = en == false ? 0 : 1;
}

/**
 * Set the animation time of tab view when a new tab is loaded
 * @param tabview pointer to Tab view object
 * @param anim_time_ms time of animation in milliseconds
 */
void lv_watch_tabview_set_anim_time(lv_obj_t * tabview, uint16_t anim_time)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);
#if LV_USE_ANIMATION == 0
    anim_time = 0;
#endif
    ext->anim_time = anim_time;
}

/**
 * Enable the edge flash effect. (Show an arc when the an edge is reached)
 * @param tileview pointer to a tabview
 * @param en true or false to enable/disable end flash
 */
void lv_watch_tabview_set_edge_flash(lv_obj_t * tabview, bool en)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    ext->page_hor_edge_flash_en = (en == true ? 1 : 0);
}

void lv_watch_tabview_set_slide_up_press_lost_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_up_press_lost_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_slide_down_press_lost_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_down_press_lost_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_slide_left_press_lost_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_left_press_lost_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_slide_right_press_lost_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_right_press_lost_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_long_press_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->long_press_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_slide_up_pressing_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_up_pressing_cb = (void(*)(void *))cb;
}

void lv_watch_tabview_set_slide_down_pressing_cb(const lv_obj_t * tabview, void (*cb)(lv_obj_t *))
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext == NULL) return;

    ext->slide_down_pressing_cb = (void(*)(void *))cb;
}

lv_obj_t ** lv_watch_tabview_set_show_leds_en(lv_obj_t * tabview, uint8_t show_leds_en)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    ext->show_leds_en = show_leds_en;

    /*Init leds*/
    if(ext->show_leds_en) {
        if(ext->leds) return ext->leds;
        ext->leds_content = lv_cont_create(tabview, NULL);
        LV_ASSERT_MEM(ext->leds_content);
        if(ext->leds_content == NULL) return NULL;
        lv_cont_set_fit(ext->leds_content, LV_FIT_TIGHT);
        lv_cont_set_layout(ext->leds_content, LV_LAYOUT_ROW_MID);
        lv_obj_add_style(ext->leds_content, LV_CONT_PART_MAIN, &lv_watch_style_transp);
        lv_obj_set_click(ext->leds_content, false);

        ext->leds = lv_mem_alloc(sizeof(lv_obj_t *) * (ext->tab_cnt));
        LV_ASSERT_MEM(ext->leds);
        if(ext->leds == NULL) return NULL;
        for(uint8_t i = 0; i < ext->tab_cnt; i++) {
            ext->leds[i] = lv_led_create(ext->leds_content, NULL);
            //printf("ext->led[%d]=%p, objptr=%p\n", i, &ext->leds[i], ext->leds[i]);
        }

        lv_watch_tabview_leds_update(tabview);
        lv_obj_align(ext->leds_content, tabview, LV_ALIGN_IN_BOTTOM_MID, 0, 0);

    } else {
        if(ext->leds_content) {
            ext->leds_content = NULL;
            if(ext->leds) {
                lv_mem_free(ext->leds);
                ext->leds = NULL;
            }
        }
    }

    return ext->leds;
}

/*=====================
 * Getter functions
 *====================*/
/**
 * Get the index of the currently active tab
 * @param tabview pointer to Tab view object
 * @return the active btn index
 */
uint16_t lv_watch_tabview_get_tab_act(const lv_obj_t * tabview)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);
    return ext->tab_cur;
}

/**
 * Get the number of tabs
 * @param tabview pointer to Tab view object
 * @return btn count
 */
uint16_t lv_watch_tabview_get_tab_count(const lv_obj_t * tabview)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);
    return ext->tab_cnt;
}

/**
 * Get the page (content area) of a tab
 * @param tabview pointer to Tab view object
 * @param id index of the btn (>= 0)
 * @return pointer to page (lv_page) object
 */
lv_obj_t * lv_watch_tabview_get_tab(const lv_obj_t * tabview, uint16_t id)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    uint16_t i = 0;
    lv_obj_t * page = lv_obj_get_child_back(ext->content, NULL);

    while(page != NULL && i != id) {
        i++;
        page = lv_obj_get_child_back(ext->content, page);
    }

    if(i == id) return page;

    return NULL;
}

/**
 * Get horizontal sliding is enabled or not
 * @param tabview pointer to Tab view object
 * @return true: enable sliding; false: disable sliding
 */
bool lv_watch_tabview_get_sliding(const lv_obj_t * tabview)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    return ext->slide_enable ? true : false;
}

/**
 * Signal function of the Tab view
 * @param tabview pointer to a Tab view object
 * @param sign a signal type from lv_signal_t enum
 * @param param pointer to a signal specific variable
 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
 */
lv_res_t lv_watch_tabview_signal(lv_obj_t * tabview, lv_signal_t sign, void * param)
{
    lv_res_t res;

    /* Include the ancient signal function */
    res = ancestor_signal(tabview, sign, param);
    if(res != LV_RES_OK) return res;

    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(sign == LV_SIGNAL_CLEANUP) {
        ext->content = NULL;
        ext->leds_content = NULL;
        if(ext->leds) {
            lv_mem_free(ext->leds);
            ext->leds = NULL;
        }
    } else if(sign == LV_SIGNAL_GET_TYPE) {
        lv_obj_type_t * buf = param;
        uint8_t i;
        for(i = 0; i < LV_MAX_ANCESTOR_NUM - 1; i++) {  /*Find the last set data*/
            if(buf->type[i] == NULL) break;
        }
        buf->type[i] = "lv_watch_tabview";
    }

    return res;
}

lv_obj_t * lv_watch_tabview_get_from_obj(lv_obj_t * obj)
{
    /*Get the lv_watch_tabview from the element*/
    lv_obj_t * tabview = lv_obj_get_child(obj, NULL);
    while(tabview) {
        if(lv_obj_get_signal_cb(tabview) != lv_watch_tabview_signal) tabview = lv_obj_get_child(tabview, NULL);
        else break;
    }
    return tabview;
}


/**********************
 *   STATIC FUNCTIONS
 **********************/
static lv_res_t element_signal_func(lv_obj_t * element, lv_signal_t sign, void * param)
{
    lv_res_t res;

    //printf("lv_tabview: element_signal_func, sign=%d\n", sign);
    /* if not click signs, invoke ancient handler and return */
    if(sign != LV_SIGNAL_PRESSING && sign != LV_SIGNAL_PRESSED && sign != LV_SIGNAL_RELEASED && sign != LV_SIGNAL_PRESS_LOST) {
        /* Include the ancient signal function */
        lv_signal_cb_t sign_func = (lv_signal_cb_t)(lv_obj_get_user_data(element)).user_data;
        res = sign_func(element, sign, param);
        if(res != LV_RES_OK) return res;

        return LV_RES_OK;
    }

    lv_obj_t * tab_page = lv_obj_get_parent(element);
    while(tab_page) {
        if(lv_obj_get_signal_cb(tab_page) != tabpage_signal) tab_page = lv_obj_get_parent(tab_page);
        else break;
    }
    if(tab_page == NULL) return LV_RES_INV;

    //printf("invoke tabpage_signal, input element used for get client attribute from element.\n");
    /* eg: recent record has a ver list, the element should not send ver sliding signal to watch tab view.
     * but hor sliding signal need for tab switch
     */
    tabpage_signal_from_element(tab_page, sign, param, element);

    lv_obj_t * cont = lv_obj_get_parent(tab_page);
    lv_obj_t * tabview = lv_obj_get_parent(cont);
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(!ext->element_click_en) {
        //printf("ext->element_click_en = %d return ,not do btn sign\n", ext->element_click_en);
        if(sign == LV_SIGNAL_PRESSING || sign == LV_SIGNAL_PRESSED) {
            return LV_RES_OK;
        } else if(sign == LV_SIGNAL_RELEASED) {
            /* Although lv_list and lv_btn check lv_indev_is_dragging before RELEASED action.
             * If drag slowly, lv_indev_is_dragging = false. when drag animation is working.
             * So change LV_SIGNAL_RELEASED to LV_SIGNAL_PRESS_LOST, the click action will not be invoked when dragging animation.
             * LV_SIGNAL_PRESS_LOST makes button and list state Recovered.
             * eg: if tabview is dragging, the pressed btn will not be high lighted*/
            lv_watch_indev_set_draged(element);
            sign = LV_SIGNAL_PRESS_LOST;
        }
    }

    /* Include the ancient signal function */
    lv_signal_cb_t sign_func = (lv_signal_cb_t)(lv_obj_get_user_data(element).user_data);
    res = sign_func(element, sign, param);
    if(res != LV_RES_OK) return res;

    return LV_RES_OK;
}

/**
 * Signal function of a tab's page
 * @param tab pointer to a tab page object
 * @param sign a signal type from lv_signal_t enum
 * @param param pointer to a signal specific variable
 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
 */
static lv_res_t tabpage_signal(lv_obj_t * tab_page, lv_signal_t sign, void * param)
{
    return tabpage_signal_from_element(tab_page, sign, param, NULL);
}

static lv_res_t tabpage_signal_from_element(lv_obj_t * tab_page, lv_signal_t sign, void * param, lv_obj_t * element)
{
    lv_res_t res;

    /* Include the ancient signal function */
    res = page_signal(tab_page, sign, param);
    if(res != LV_RES_OK) return res;

    lv_obj_t * cont = lv_obj_get_parent(tab_page);
    lv_obj_t * tabview = lv_obj_get_parent(cont);

    if(lv_watch_tabview_get_sliding(tabview) == false) return res;

    if(sign == LV_SIGNAL_PRESSED) {
        tabpage_pressed_handler(tabview, tab_page, element);
    } else if(sign == LV_SIGNAL_PRESSING) {
        tabpage_pressing_handler(tabview, tab_page, element);
    } else if(sign == LV_SIGNAL_RELEASED || sign == LV_SIGNAL_PRESS_LOST) {
        tabpage_press_lost_handler(tabview, tab_page, element);
    } else if(sign == LV_SIGNAL_LONG_PRESS) {
        tabpage_long_press_handler(tabview, tab_page);
    }

    return res;
}

/**
 * Signal function of the tab page's scrollable object
 * @param tab_scrl pointer to a tab page's scrollable object
 * @param sign a signal type from lv_signal_t enum
 * @param param pointer to a signal specific variable
 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
 */
static lv_res_t tabpage_scrl_signal(lv_obj_t * tab_scrl, lv_signal_t sign, void * param)
{
    lv_res_t res;

    /* Include the ancient signal function */
    res = page_scrl_signal(tab_scrl, sign, param);
    if(res != LV_RES_OK) return res;

    lv_obj_t * tab_page = lv_obj_get_parent(tab_scrl);
    lv_obj_t * cont = lv_obj_get_parent(tab_page);
    lv_obj_t * tabview = lv_obj_get_parent(cont);

    if(lv_watch_tabview_get_sliding(tabview) == false) return res;

    if(sign == LV_SIGNAL_PRESSED) {
        tabpage_pressed_handler(tabview, tab_page, NULL);
    } else if(sign == LV_SIGNAL_PRESSING) {
        tabpage_pressing_handler(tabview, tab_page, NULL);
    } else if(sign == LV_SIGNAL_RELEASED || sign == LV_SIGNAL_PRESS_LOST) {
        tabpage_press_lost_handler(tabview, tab_page, NULL);
    } else if(sign == LV_SIGNAL_LONG_PRESS) {
        tabpage_long_press_handler(tabview, tab_page);
    }

    return res;
}

/**
 * Called when a tab's page or scrollable object is pressed
 * @param tabview pointer to the btn view object
 * @param tabpage pointer to the page of a btn
 */
static void tabpage_pressed_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element)
{
    (void)tabpage;

    //printf("tabpage_pressed_handler\n");
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    lv_indev_t * indev = lv_indev_get_act();
    lv_indev_get_point(indev, &ext->point_last);
    ext->point_start.x = ext->point_last.x;
    ext->point_start.y = ext->point_last.y;
    ext->point_now.x = ext->point_last.x;
    ext->point_now.y = ext->point_last.y;
    //printf("[pressed]: point_now.x=%d, point_now.y=%d, point_start.x=%d, point_start.y=%d\n",\
    //    ext->point_now.x, ext->point_now.y, ext->point_start.x, ext->point_start.y);

}

/**
 * Called when a tab's page or scrollable object is being pressed
 * @param tabview pointer to the btn view object
 * @param tabpage pointer to the page of a btn
 */
static void tabpage_pressing_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    lv_indev_t * indev = lv_indev_get_act();
    lv_point_t point_act;
    lv_indev_get_point(indev, &point_act);
    lv_coord_t x_diff = point_act.x - ext->point_last.x;
    lv_coord_t y_diff = point_act.y - ext->point_last.y;

    //printf("x_diff=%d, y_diff=%d\n", x_diff, y_diff);

    lv_watch_tabview_element_free_ptr_client_t * ext_client = NULL;
    ext_client = element_get_ext_client(element);
    if(ext_client != NULL) {
        //printf("ext_client->sliding_down_en=%d, ext_client->sliding_up_en=%d, ext_client->sliding_left_en=%d, ext_client->sliding_right_en=%d\n", \
        //    ext_client->sliding_down_en, ext_client->sliding_up_en, ext_client->sliding_left_en, ext_client->sliding_right_en);

        if(abs(x_diff) < abs(y_diff) && !ext_client->sliding_down_en && y_diff > ext->ver_drag_start) {
            //printf("-----element ignore sliding_down signal\n");
            return;
        }

        if(abs(x_diff) < abs(y_diff) && !ext_client->sliding_up_en && y_diff < -ext->ver_drag_start) {
            //printf("-----element ignore sliding_up signal\n");
            return;
        }

        if(abs(x_diff) > abs(y_diff) && !ext_client->sliding_left_en && x_diff < -ext->hor_drag_start) {
            //printf("-----element ignore sliding_left signal\n");
            return;
        }

        if(abs(x_diff) > abs(y_diff) && !ext_client->sliding_right_en && x_diff > ext->hor_drag_start) {
            //printf("-----element ignore sliding_right signal\n");
            return;
        }
    }

    ext->point_now.x = point_act.x;
    ext->point_now.y = point_act.y;

    //printf("[pressing]: point_now.x=%d, point_now.y=%d, point_start.x=%d, point_start.y=%d\n",\
    //    ext->point_now.x, ext->point_now.y, ext->point_start.x, ext->point_start.y);

    //printf("tabpage_pressing_handler\n");
    if(ext->draging == 0) {
        if(abs(x_diff) > abs(y_diff) && abs(x_diff) > ext->hor_drag_start) {
            ext->drag_hor = 1;
            ext->draging = 1;
            lv_obj_set_drag(lv_page_get_scrl(tabpage), false);
            //printf("set ext->drag_hor = 1\n");
            ext->element_click_en = 0;
            ext->is_drag_right = (x_diff >= ext->hor_drag_start) ? 1 : 0;
        } else if(abs(y_diff) >= ext->ver_drag_start) {
            ext->drag_hor = 0;
            ext->draging = 1;
            ext->element_click_en = 0;
            ext->is_drag_up = (y_diff >=  ext->ver_drag_start) ? 0 : 1;
            //launcher_clean_hide_tabs(tabview);
        }
    }

    /* hor silding show limit */
    if(ext->drag_hor) {
        /* hor sliding to the end pages */
        lv_coord_t new_x = lv_obj_get_x(ext->content) + point_act.x - ext->point_last.x;
        lv_coord_t left_limit = 0;
        lv_coord_t right_limit = -(lv_obj_get_width(ext->content) - lv_obj_get_width(tabview) + left_limit);

        if(new_x >= left_limit || new_x <= right_limit) {
            new_x = (new_x >= 0 ? left_limit : right_limit);
            lv_obj_set_x(ext->content, new_x);
        } else {
            lv_obj_set_x(ext->content, new_x);
        }
    } else if(ext->draging) {
        /* ver sliding*/
        if(!ext->is_drag_up && ext->slide_down_pressing_cb) {
            ext->slide_down_pressing_cb(tabview);
        } else if(ext->is_drag_up && ext->slide_up_pressing_cb) {
            ext->slide_up_pressing_cb(tabview);
        }
    }

    ext->point_last.x = point_act.x;
    ext->point_last.y = point_act.y;
}

/**
 * Called when a tab's page or scrollable object is released or the press id lost
 * @param tabview pointer to the btn view object
 * @param tabpage pointer to the page of a btn
 */
static void tabpage_press_lost_handler(lv_obj_t * tabview, lv_obj_t * tabpage, lv_obj_t * element)
{
    //printf("tabpage_press_lost_handler\n");
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);

    /* if count acceleration must after drag set recover. */
    /* hor slide : slide up/down callback*/
    /*ext->element_click_en = 1, must invoked after animation finished. */
    if(ext->drag_hor) {
        /* hor sliding*/
        tabpage_start_edge_flash(tabview, tabpage);
    } else if(ext->draging) {
        /* ver sliding*/
        if(!ext->is_drag_up && ext->slide_down_press_lost_cb) {
            ext->slide_down_press_lost_cb(tabview);
        } else if(ext->is_drag_up && ext->slide_up_press_lost_cb) {
            ext->slide_up_press_lost_cb(tabview);
        }
    }

    ext->drag_hor = 0;
    ext->draging = 0;

    lv_obj_set_drag(lv_page_get_scrl(tabpage), true);

    lv_indev_t * indev = lv_indev_get_act();
    lv_point_t point_act;
    lv_indev_get_point(indev, &point_act);
    lv_point_t vect;
    lv_indev_get_vect(indev, &vect);
    lv_coord_t x_predict = 0;

    while(vect.x != 0)   {
        x_predict += vect.x;
        vect.x = vect.x * (100 - LV_INDEV_DEF_DRAG_THROW) / 100;
    }

    lv_coord_t page_x1 = tabpage->coords.x1 - tabview->coords.x1 + x_predict;
    lv_coord_t page_x2 = page_x1 + lv_obj_get_width(tabpage);
    lv_coord_t treshold = lv_obj_get_width(tabview) / 2;
    uint16_t tab_cur = ext->tab_cur;

    /* hor sliding loop mode*/
    if(ext->hor_sliding_loop_en) {
        if(page_x1 > treshold) {
            if(tab_cur > 1) {
                tab_cur--;
            } else {
                lv_obj_set_x(ext->content,  lv_obj_get_x(ext->content) - LV_HOR_RES * (ext->tab_cnt - 2));
                tab_cur = ext->tab_cnt - 2;
            }
        } else if(page_x2 < treshold) {
            if(tab_cur < ext->tab_cnt - 2) {
                tab_cur++;
            } else {
                if(tab_cur == ext->tab_cnt - 2) {
                    lv_coord_t x_diff = lv_obj_get_x(ext->content) + (ext->tab_cnt - 2) * LV_HOR_RES;
                    if(x_diff < LV_INDEV_DEF_DRAG_THROW - LV_HOR_RES) {
                        tab_cur = ext->tab_cnt - 2;
                    } else {
                        lv_obj_set_x(ext->content, lv_obj_get_x(ext->content) + LV_HOR_RES * (ext->tab_cnt - 2));
                        tab_cur = 1;
                    }
                }
            }
        }
    } else {
        /* hor sliding normal mode*/
        if(page_x1 > treshold) {
            if(tab_cur != 0) tab_cur--;
        } else if(page_x2 < treshold) {
            if(tab_cur < ext->tab_cnt - 1) tab_cur++;
        }
    }

    uint32_t id_prev = lv_watch_tabview_get_tab_act(tabview);
    lv_watch_tabview_set_tab_act(tabview, tab_cur, true);
    uint32_t id_new = lv_watch_tabview_get_tab_act(tabview);

    if(id_prev != id_new) {
        lv_event_send(tabview, LV_EVENT_VALUE_CHANGED, &id_prev);
    }
}

static void tabpage_long_press_handler(lv_obj_t * tabview, lv_obj_t * tabpage)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(ext->long_press_cb &&  ext->draging == 0) {
        ext->long_press_cb(tabview);
    }
}

static void tabpage_start_edge_flash(lv_obj_t * tabview, lv_obj_t * tabpage)
{
    lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
    if(!ext->page_hor_edge_flash_en) {
        return;
    }

    uint16_t tab_cur = ext->tab_cur;
    lv_indev_t * indev = lv_indev_get_act();

    if(ext->drag_hor && tab_cur == 0 && indev->proc.types.pointer.vect.x >= 0) {
        if(!ext->slide_left_press_lost_cb) {
            lv_page_ext_t * page_ext = lv_obj_get_ext_attr(tabpage);
            lv_page_set_edge_flash(tabpage, true);
            if(page_ext->edge_flash.enabled &&
                    page_ext->edge_flash.left_ip == 0 && page_ext->edge_flash.right_ip == 0 &&
                    page_ext->edge_flash.top_ip == 0 && page_ext->edge_flash.bottom_ip == 0) {
                page_ext->edge_flash.left_ip = 1;
                lv_page_start_edge_flash(tabpage, LV_PAGE_EDGE_LEFT);
            }
        } else {
            ext->slide_left_press_lost_cb(tabview);
        }
    } else if(ext->drag_hor && tab_cur ==  ext->tab_cnt - 1 && indev->proc.types.pointer.vect.x <= 0) {
        if(!ext->slide_right_press_lost_cb) {
            lv_page_ext_t * page_ext = lv_obj_get_ext_attr(tabpage);
            lv_page_set_edge_flash(tabpage, true);
            if(page_ext->edge_flash.enabled &&
                    page_ext->edge_flash.left_ip == 0 && page_ext->edge_flash.right_ip == 0 &&
                    page_ext->edge_flash.top_ip == 0 && page_ext->edge_flash.bottom_ip == 0) {
                page_ext->edge_flash.right_ip = 1;
                lv_page_start_edge_flash(tabpage, LV_PAGE_EDGE_RIGHT);
            }
        } else {
            ext->slide_right_press_lost_cb(tabview);
        }
    }
}

static void lv_tabview_set_tab_act_finish_callback(lv_anim_t * a)
{
    lv_obj_t * content = a->var;
    lv_obj_t * tabview = lv_obj_get_parent(content);
    if(tabview->signal_cb == lv_watch_tabview_signal) {
        lv_watch_tabview_ext_t * ext = lv_obj_get_ext_attr(tabview);
        ext->element_click_en = 1;
        //lv_watch_obj_png_release(lv_watch_tabview_get_tab(tabview, ext->tab_cur));
        //lv_watch_png_cache_all_free();
        lv_watch_tabview_leds_update(tabview);
    }
}

static void lv_watch_tabview_leds_update(lv_obj_t * tabview)
{
    lv_watch_tabview_ext_t  * ext = lv_obj_get_ext_attr(tabview);

    if(ext->show_leds_en && ext->leds) {
        if((ext->show_leds_en & (1 << ext->tab_cur)) == 0) {
            lv_obj_set_hidden(ext->leds_content, true);
        } else {
            lv_obj_set_hidden(ext->leds_content, false);
        }

        for(uint8_t i = 0; i < ext->tab_cnt; i++) {
            if(ext->leds[i] == NULL) continue;
            if(i == ext->tab_cur) {
                lv_led_on(ext->leds[i]);
                lv_obj_set_size(ext->leds[i], LV_WATCH_TABVIEW_LED_ON_WIDTH, LV_WATCH_TABVIEW_LED_ON_WIDTH);
            } else {
                lv_led_off(ext->leds[i]);
                lv_obj_set_size(ext->leds[i], LV_WATCH_TABVIEW_LED_OFF_WIDTH, LV_WATCH_TABVIEW_LED_OFF_WIDTH);

            }
        }
    }
}

static lv_watch_tabview_element_free_ptr_client_t * element_get_ext_client(lv_obj_t * element)
{
    if(element == NULL) return NULL;
    return lv_obj_get_user_data(element).user_data_for_client;
}

#endif
